﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data;

namespace GoodStuff.Data.Linq
{
    /// <summary>
    /// Base class which can be used in a LinqToSql solution. The purpose of this baseclass is to be forced to think about objecttracking and dataloadoptions.
    /// </summary>
    public abstract class LinqToSqlRepository<TDataContext> : IDisposable where TDataContext : DataContext
    {
        private TDataContext _dataContext = null;
        private System.Data.Common.DbTransaction _currentTransaction;

        /// <summary>
        /// Constructor
        /// </summary>
        protected LinqToSqlRepository()
        {       
        }

        /// <summary>
        /// Should return a DataContext, without any LoadOptions preloaded.
        /// </summary>
        /// <returns>a DataContext which we can use.</returns>
        protected abstract TDataContext CreateDataContext();

        /// <summary>
        /// Forces to retrieve full dataloadoptions, so including all delayed loaded properties. Will only be used if the DataContext is retrieved from
        /// the base class with fullDataLoadOptions=true. Otherwise this method isn't invoked.
        /// </summary>
        /// <returns>collection of DataLoadOptions which will return all properties and related data.</returns>
        protected abstract DataLoadOptions CreateFullDataLoadOptions();

        /// <summary>
        /// Get's the DataContext. Creates a DataContext (implemented by subclass) and enable Traceoutput when the application is compiled in DEBUG mode.
        /// </summary>
        /// <param name="objectTracking">use objectTracking only when you're going go to submit changes to the datacontext.
        /// When objecttracking is set to false, performance will be better. When objecttracking is false, no
        /// related/subdata will be loaded in a smart way. In that case, ensure your're applying the right
        /// dataloadoptions by yourself.</param>
        /// <param name="fullDataLoadOptions">Do we have to load full dataloadoptions retrieved by CreateFullDataLoadOptions()? Should
        /// only be the case when retrieving all properties and related/subdata of an object. When false, no DataLoadOptions will be loaded.</param>
        /// <returns>DataContext with objecttracking true/false and optional full DataLoadOptions</returns>
        protected TDataContext GetDataContext(bool objectTracking, bool fullDataLoadOptions)
        {
            _dataContext = this.CreateDataContext();
#if DEBUG
            _dataContext.Log = new GoodStuff.Diagnostics.TraceTextWriter();
#endif     

            if (_dataContext == null)
                return null;

            _dataContext.ObjectTrackingEnabled = objectTracking;
            if (fullDataLoadOptions)
            {
                _dataContext.LoadOptions = this.CreateFullDataLoadOptions();
            }
            return _dataContext;
        }

        /// <summary>
        /// Get's the DataContext. Creates a DataContext (implemented by subclass) and enable Traceoutput when the application is compiled in DEBUG mode.
        /// </summary>
        /// <param name="objectTracking">use objectTracking only when you're going go to submit changes to the datacontext.
        /// When objecttracking is set to false, performance will be better. When objecttracking is false, no
        /// related/subdata will be loaded in a smart way. In that case, ensure your're applying the right
        /// dataloadoptions by yourself. When you don't want to use DataLoadOptions, specify null </param>
        /// <param name="dataLoadOptions">dataLoadOptions which will be applied to the datacontext</param>
        /// <returns>DataContext with objecttracking true/false and specified DataLoadOptions</returns>
        protected TDataContext GetDataContext(bool objectTracking, DataLoadOptions dataLoadOptions)
        {
            _dataContext = this.CreateDataContext();
#if DEBUG
            _dataContext.Log = new GoodStuff.Diagnostics.TraceTextWriter();
#endif     

            if (_dataContext == null)
                return null;

            _dataContext.ObjectTrackingEnabled = objectTracking;

            if (dataLoadOptions != null)
            {
                _dataContext.LoadOptions = dataLoadOptions;
            }

            return _dataContext;
        }

        /// <summary>
        /// Starts a database-transaction. Throws when there already is an open transaction
        /// </summary>
        protected void BeginTransaction()
        {
            if (_currentTransaction != null)
            {
                throw new Exception("A transaction is already open");
            }
            if (_dataContext.Connection.State != System.Data.ConnectionState.Open)
            {
                _dataContext.Connection.Open();
            }
            _currentTransaction = _dataContext.Connection.BeginTransaction();
            _dataContext.Transaction = _currentTransaction;
        }

        /// <summary>
        /// Starts a database-transaction with the specified isolationlevel. Throws when there already is an open transaction
        /// </summary>
        /// <param name="isolationLevel">the isolationlevel of the transaction</param>
        protected void BeginTransaction(IsolationLevel isolationLevel)
        {
            if (_currentTransaction != null)
            {
                throw new Exception("A transaction is already open");
            }
            if (_dataContext.Connection.State != System.Data.ConnectionState.Open)
            {
                _dataContext.Connection.Open();
            }
            _currentTransaction = _dataContext.Connection.BeginTransaction(isolationLevel);
            _dataContext.Transaction = _currentTransaction;
        }

        /// <summary>
        /// Revert/Rollback the current database-transaction. Throws when there is no current transaction
        /// </summary>
        protected void RollBack()
        {
            if (_currentTransaction == null)
            {
                throw new Exception("A transaction is required");
            }

            _currentTransaction.Rollback();
            _currentTransaction = null;
        }

        /// <summary>
        /// Commits the current transaction to the database. Throws when there is no current transaction
        /// </summary>
        protected void Commit()
        {
            if (_currentTransaction == null)
            {
                throw new Exception("A transaction is required");
            }

            _currentTransaction.Commit();
            _currentTransaction = null;
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (_dataContext != null)
            {
                _dataContext.Dispose();
                _dataContext = null;
            }
        }

        #endregion
    }
}
